package xcmg.device.infra.interceptor.externalApiCheck.handle;


import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xcmg.device.dao.entity.ApiCheckAcc;
import xcmg.device.infra.DateUtil;
import xcmg.device.infra.ErrorCode;
import xcmg.device.infra.JsonUtil;
import xcmg.device.infra.interceptor.externalApiCheck.handle.ApiCheckStrategy.ApiCheckService;
import xcmg.device.service.ApiCheckAccService;
import xcmg.device.util.JedisUtils;
import xcmg.device.util.SecretUtils;
import yb.ecp.fast.infra.infra.ActionResult;
import yb.ecp.fast.infra.infra.log.LogHelper;

/**
   验证方式：需要在http请求头中设置参数“sign”，作为验证的token，生成“sign”值的方式如下：
   key : hjkuyhg679hytG6r
   secret : jh89Ksn7t483jy6d65GJ0oi7yn65d5wg
   timestamp ：接口调用时的时间戳（精确到秒，长度是10位）
   md5Str : secret + key + timestamp + secret拼接后的字符串, 用MD5加密成的32位字符

   sign = md5Str  +  timestamp  +  key

 * 一般MD5校验会带入请求的参数，由于备件系统接口特殊性，基本都是headinfo，detailList之类的，很多接口不适合将参数名拼到字符串中加密，所以此处
 * 只用了key和secret以及时间戳来校验，方案2提供参数拼接的校验方式，可以提供给适合的接口
 *
 * @author wangyong
 */
@Component("apiCheckOption1Service")
public class ApiCheckOption1Service implements ApiCheckService {

  @Autowired
  private ApiCheckAccService apiCheckAccService;

  @Autowired
  private JedisUtils jedisUtils;
  /**
   * 接口允许调用的时间范围。与当前时间相差5分钟以内
   */
  private static final int ALLOWABLE_TIME_RANGE = 5;

  private static final String REQUEST_HEADER_TOKEN_NAME = "sign";

  @Override
  public boolean doCheck(HttpServletRequest request, HttpServletResponse response) {
    String token = request.getHeader(REQUEST_HEADER_TOKEN_NAME);
    PrintWriter writer = null;
    try {
      if (StringUtils.isBlank(token)) {
        writer = response.getWriter();
        writer.print(getErrorReksult("no permission to call! "));
        return false;
      }
      String key = token.substring(42);
      LogHelper.monitor(request.getRequestURI() + ", token: " + token + ", key = " + key);

      ApiCheckAcc acc = getApiCheckAcc(key);
      if (null == acc) {
        writer = response.getWriter();
        writer.print(getErrorReksult("no permission to call! acc is null"));
        return false;
      }
      if (!secretCheckAllow(token, acc)) {
        writer = response.getWriter();
        writer.print(getErrorReksult("no permission to call! valid failure"));
        return false;
      }
      if (!isAllowTime(Long.valueOf(token.substring(32, 42)), acc.getRequestPeriod())) {
        writer = response.getWriter();
        writer = response.getWriter();
        writer.print(getErrorReksult("call timed out."));
        return false;
      }
    } catch (Exception e) {
      LogHelper.fatal("api doCheck is error", e);
      return false;
    } finally {
      if (null != writer) {
        writer.close();
      }
    }
    return true;
  }

  private ApiCheckAcc getApiCheckAcc(String key) {
    String redisKey = "API_CHECK_KEY_" + key;
    if (jedisUtils.exists(redisKey)) {
      return (ApiCheckAcc) jedisUtils.get(redisKey);
    }
    ApiCheckAcc acc = apiCheckAccService.getByKey(key);
    // 过期时间10分钟，主要是解决定时任务频繁调用接口校验查询多次的问题
    jedisUtils.set(redisKey, acc, 60*10L);
    return acc;
  }

  private String getErrorReksult(String errorInfo) {
    return JsonUtil.objectToString(new ActionResult(ErrorCode.Failure.getCode(), errorInfo));
  }

  private boolean secretCheckAllow(String token, ApiCheckAcc acc) {
    String md5Sec = token.substring(0, 32);
    String timestampStr = token.substring(32, 42);
    StringBuilder tokenSource = new StringBuilder();
    tokenSource.append(acc.getAppSecret()).append(acc.getAppKey()).append(timestampStr)
        .append(acc.getAppSecret());
    return md5Sec.equals(SecretUtils.genMD5(tokenSource.toString()).toUpperCase());
  }

  private boolean isAllowTime(Long timestamp, Integer requestPeriod) {
    Integer period = null == requestPeriod ? ALLOWABLE_TIME_RANGE : requestPeriod;
    Date now = new Date();
    Date apiTime = new Date(timestamp * 1000);
    Date allowTimeBefore = DateUtil.getMinuteAddTime(now, -period);
    Date allowTimeAfter = DateUtil.getMinuteAddTime(now, period);
    return apiTime.before(allowTimeAfter) && apiTime.after(allowTimeBefore);
  }
}
